<template>
  <div
    class="my-carousel"
    :style="{ height: height }"
    @mouseenter.stop="handleMouseEnter"
    @mouseleave.stop="handleMouseLeave"
  >
    <div class="my-carousel__dots">
      <div
        class="my-carousel__dot"
        :class="[activeIndex==index?'active':'']"
        v-for="item,index in items"
        :key="index"
        @click="handleClickDot(index)"
      ></div>
    </div>
    <template v-if="showArrow && items.length > 1">
      <div class="my-carousel__arrow my-carousel__arrow--left" @click="throttleClickPrev">
        <i class="my-icon-arrow-left"></i>
      </div>
      <div class="my-carousel__arrow my-carousel__arrow--right" @click="throttleClickNext">
        <i class="my-icon-arrow-right"></i>
      </div>
    </template>

    <div class="my-carousel__list" ref="carousel" :style="carouselStyle">
      <slot></slot>
    </div>
  </div>
</template>

<script>
import { throttle } from "throttle-debounce";
export default {
  name: "MyCarousel",
  props: {
    height: String,
    showArrow: Boolean,
    autoplay: Boolean,
    interval: {
      type: Number,
      default: 3,
      validator: (n) => n >= 1,
    },
  },
  data() {
    return {
      carouselStyle: {
        transform: `translateX(0px)`,
        transitionDuration: `0s`,
      },
      transform: "0px",
      transitionDuration: "0s",
      activeIndex: 0,
      items: [],
      timer: null,
    };
  },
  created() {
    this.throttleClickPrev = throttle(400, this.handleClickPrev);
    this.throttleClickNext = throttle(400, this.handleClickNext);
  },
  mounted() {
    this.updateItems();
    this.$nextTick(() => {
      this.$refs.carousel.addEventListener(
        "transitionend",
        this.cancelTransition
      );
      this.startTimer();
    });
  },
  beforeDestroy() {
    this.$refs.carousel.removeEventListener(
      "transitionend",
      this.cancelTransition
    );
    this.pauseTimer();
  },
  watch: {
    autoplay(val) {
      val ? this.startTimer() : this.pauseTimer();
    },
  },
  methods: {
    calcCarouselStyle(direction) {
      let { width } = this.$el.getBoundingClientRect();
      let index = this.activeIndex;
      let itemsLen = this.items.length;
      switch (direction) {
        case "prev":
          if (index === itemsLen - 1) {
            this.carouselStyle.transform = `translateX(${
              0 - width * itemsLen
            }px)`;
          }
          setTimeout(() => {
            this.carouselStyle.transitionDuration = "0.3s";
            this.carouselStyle.transform = `translateX(${0 - width * index}px)`;
          });
          break;
        case "next":
          if (index === 0) {
            this.carouselStyle.transitionDuration = "0.3s";
            this.carouselStyle.transform = `translateX(${
              0 - width * itemsLen
            }px)`;
            let fuc = () => {
              this.carouselStyle.transitionDuration = "0s";
              this.carouselStyle.transform = `translateX(0px)`;
              this.$refs.carousel.removeEventListener("transitionend", fuc);
            };
            this.$refs.carousel.addEventListener("transitionend", fuc);
          } else {
            this.carouselStyle.transitionDuration = "0.3s";
            this.carouselStyle.transform = `translateX(${0 - width * index}px)`;
          }
          break;
      }
    },
    cancelTransition() {
      this.carouselStyle.transitionDuration = "0s";
    },
    handleClickPrev() {
      if (this.items.length < 2) return;
      if (this.activeIndex > 0) {
        this.activeIndex -= 1;
      } else {
        this.activeIndex = this.items.length - 1;
      }
      this.calcCarouselStyle("prev");
    },
    handleClickNext() {
      if (this.items.length < 2) return;
      if (this.activeIndex < this.items.length - 1) {
        this.activeIndex += 1;
      } else {
        this.activeIndex = 0;
      }
      this.calcCarouselStyle("next");
    },
    handleClickDot(index) {
      let direction = index > this.activeIndex ? "next" : "prev";
      this.activeIndex = index;
      this.calcCarouselStyle(direction);
    },
    startTimer() {
      if (this.timer || this.interval < 1 || !this.autoplay) return;
      this.timer = setInterval(this.handleClickNext, this.interval * 1000);
    },
    pauseTimer() {
      if (this.timer) {
        clearInterval(this.timer);
        this.timer = null;
      }
    },
    handleMouseEnter() {
      this.pauseTimer();
    },
    handleMouseLeave() {
      this.startTimer();
    },
    updateItems() {
      this.items = this.$slots.default
        ? this.$slots.default.map((o) => o.componentInstance)
        : [];
      if (this.items.length > 1) {
        this.$refs.carousel.appendChild(this.items[0].$el.cloneNode(true));
      }
    },
  },
};
</script>

<style lang="scss">
.my-carousel {
  box-sizing: border-box;
  position: relative;
  overflow: hidden;
}
.my-carousel__list {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-wrap: nowrap;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-property: transform;
}

.my-carousel__arrow {
  position: absolute;
  top: calc(50% - 14px);
  height: 28px;
  width: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(255, 255, 255, 0.3);
  border-radius: 50%;
  user-select: none;
  font-size: 12px;
  color: #ffffff;
  z-index: 10;
  cursor: pointer;
  transition: background-color 0.3s;

  &:hover {
    background-color: rgba(255, 255, 255, 0.4);
  }

  &.my-carousel__arrow--left {
    left: 10px;
  }

  &.my-carousel__arrow--right {
    right: 10px;
  }
}

.my-carousel__dots {
  position: absolute;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-wrap: nowrap;
  z-index: 10;

  .my-carousel__dot {
    background-color: rgba($color: #ffffff, $alpha: 0.4);
    width: 14px;
    height: 6px;
    border-radius: 20px;
    margin: 0 4px;
    cursor: pointer;
    transition: background-color 0.3s, width 0.3s;
    &.active {
      width: 28px;
      background-color: rgba($color: #ffffff, $alpha: 1);
    }
  }
}
</style>